﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.ComponentModel;
using System.IO;
using FinPlusAssembler;
using FinPlusInterfaces;
using Microsoft.Win32;

namespace FinPlusCompView
{
    public partial class FinPlusErrorLog : FinPlusControl
    {
        [State(Test = true)]
        public override bool IsValid { get; set; }
        [State(Save = true, Test = true)]
        public string FilePath { get { return _filePath; } set { tFile.Text = _filePath = value; } }
        [State(Save = true)]
        public new double Height { get { return base.Height; } set { base.Height = value; } }
        [State(Save = true)]
        public new double Width { get { return base.Width; } set { base.Width = value; } }

        private StringBuilder _exceptionString;
        private LogEntrys _logEntrys;
        private readonly LogEntrys _exception;
        private readonly ImageSource _filterFilled, _filterEmpty;
        private readonly ObservableCollection<CheckedListItem<string>> _levelFilters, _idFilters, _configFilters, _errorFilters;
        private readonly ObservableCollection<CheckedListItem<int>> _threadFilters;
        private readonly CollectionViewSource _viewSource;
        private DateTime? currentDateTime;
        private string _filePath;

        //construct
        public FinPlusErrorLog()
        {
            InitializeComponent();

            _logEntrys = new LogEntrys();
            _exception = new LogEntrys();
            _exceptionString = new StringBuilder();
            _levelFilters = new ObservableCollection<CheckedListItem<string>>();
            _threadFilters = new ObservableCollection<CheckedListItem<int>>();
            _idFilters = new ObservableCollection<CheckedListItem<string>>();
            _configFilters = new ObservableCollection<CheckedListItem<string>>();
            _errorFilters = new ObservableCollection<CheckedListItem<string>>();

            _viewSource = new CollectionViewSource();
            _viewSource.Filter += ViewSource_Filter;

            _filterFilled = (ImageSource)FindResource("SFilterFilled");
            _filterEmpty = (ImageSource)FindResource("SFilterEmpty");
            dataException.ItemsSource = _exception;

            listLevels.ItemsSource = _levelFilters;
            listThreads.ItemsSource = _threadFilters;
            listIds.ItemsSource = _idFilters;
            listConfigs.ItemsSource = _configFilters;
            listErrors.ItemsSource = _errorFilters;
            dateTimePicker.SelectedDate = DateTime.Now.Date;

            IsValid = true;
        }

        //common control interface
        public override void CmdExecute(IPod command)
        {
            try
            {
                CmdCapture(command);

                switch ((Cmds)Enum.Parse(typeof(Cmds), command.Name, true))
                {
                    case Cmds.Refresh: Refresh(); break;
                    case Cmds.Clear: Clear(); break;
                    case Cmds.Copy: CopyToClipboard(); break;
                    case Cmds.FileLoad: FileLoad(command["Tag"].ToString()); break;
                    case Cmds.DataEntry: DataEntry(); break;
                    case Cmds.LevelFilter: pLevel.IsOpen = true; break;
                    case Cmds.LevelErase: LevelErase(); break;
                    case Cmds.ThreadFilter: pThread.IsOpen = true; break;
                    case Cmds.ThreadErase: ThreadErase(); break;
                    case Cmds.ConfigFilter: pConfig.IsOpen = true; break;
                    case Cmds.ConfigErase: ConfigErase(); break;
                    case Cmds.IdFilter: pId.IsOpen = true; break;
                    case Cmds.IdErase: IdErase(); break;
                    case Cmds.ErrorFilter: pError.IsOpen = true; break;
                    case Cmds.ErrorErase: ErrorErase(); break;
                    case Cmds.DateTime: DateFilter(); break;
                    case Cmds.DateTimeErase: dateTimePicker.SelectedDate = DateTime.Now.Date; break;
                    case Cmds.DateTimeBack: dateTimePicker.SelectedDate = dateTimePicker.SelectedDate.Value.AddDays(-1); break;
                    case Cmds.DateTimeForward: dateTimePicker.SelectedDate = dateTimePicker.SelectedDate.Value.AddDays(+1); break;
                    case Cmds.Filters: FilterApply(); break;
                    case Cmds.FileErase: FileErase(); break;
                    default: throw new Exception(string.Format("cmd not recognised {0}", command.Name));
                }
            }
            catch (CmdExecuteException e)
            {
                IsValid = Level.Warning.Log(Id, Config, e.Message, e);
            }
        }

        //private
        private void ClearFilters()
        {

            //clear filters
            _idFilters.Clear();
            _configFilters.Clear();
            _levelFilters.Clear();
            _threadFilters.Clear();
            _errorFilters.Clear();
            imageThread.Source = imageError.Source = imageLevel.Source = _filterEmpty;

            //clear erro log
            _logEntrys.Clear();
            _logEntrys.LastLine = 0;
            _exception.Clear();
        }

        private bool CopyToClipboard()
        {
            Clipboard.SetText(_exceptionString.ToString());
            return true;
        }

        //private toolbar
        private void Clear()
        {
            _logEntrys.LastLine = -1;

            if (dataException.Visibility == Visibility.Visible)
            {
                _exception.Clear();
                dataException.Visibility = Visibility.Collapsed;
                return;
            }
            dataEntrys.Visibility = Visibility.Collapsed;

            ClearFilters();
        }

        private async void Refresh()
        {
            if (!IsLoaded) 
                return;

            var wait = new FinPlusWait(this, "Loading...");

            try
            {
                dataEntrys.Visibility = Visibility.Visible;
                ClearFilters();

                if (string.IsNullOrEmpty(_filePath))
                    tFile.Text = _filePath =  Logger.Path;

                var selectedDatetime = dateTimePicker.SelectedDate;

                _logEntrys = await Task.Run<LogEntrys>(() => Load(selectedDatetime));

                foreach (var l in _logEntrys.Select(i => i.Id).Distinct().ToList())
                    _idFilters.Add(new CheckedListItem<string> { Item = l, IsChecked = true });

                foreach (var l in _logEntrys.Select(i => i.Config).Distinct().ToList())
                    _configFilters.Add(new CheckedListItem<string> { Item = l, IsChecked = true });

                foreach (var l in _logEntrys.Select(i => i.Level).Distinct().ToList())
                    _levelFilters.Add(new CheckedListItem<string> { Item = l, IsChecked = true });

                foreach (var l in _logEntrys.Select(i => i.Message).Distinct().ToList())
                    _errorFilters.Add(new CheckedListItem<string> { Item = l, IsChecked = true });

                foreach (var l in _logEntrys.Select(i => i.Thread).Distinct().ToList())
                    _threadFilters.Add(new CheckedListItem<int> { Item = l, IsChecked = true });

                _viewSource.Source = _logEntrys;
                dataEntrys.ItemsSource = _viewSource.View;
            }
            catch(Exception e)
            {
                IsValid = Level.Warning.Log(Id, Config, e.Message, e);
            }

            wait.Dispose();
       }

        private LogEntrys Load(DateTime? selectedDate)
        {
            var logEntrys = new LogEntrys();
            int x = 0;
            StreamReader file = null;

            try
            {
                file = new StreamReader(_filePath);
                string line;

                while ((line = file.ReadLine()) != null)
                {
                    if (x > logEntrys.LastLine && !string.IsNullOrEmpty(line) && line[0] == '@')
                    {
                        var logEntry = new LogEntry(x, line);
                        if (logEntry.DateTime.Date == selectedDate)
                        {
                            logEntrys.Add(logEntry);
                            logEntrys.LastLine = x;
                        }
                    }
                    x++;
                }

            }
            catch(Exception e)
            {
                IsValid = Level.Warning.Log(Id, Config, e.Message, e);
                _logEntrys.Add(new LogEntry() { Line = x, Message = "Log file failed at line " + x });
            }
            finally
            {
                if (file != null) file.Close();
            }

            return logEntrys;
        }

        private void FileLoad(string filePath)
        {
            tFile.Text = _filePath;
            Refresh();
        }

        private void FileErase()
        {
            if (!string.IsNullOrEmpty(_filePath) && _filePath.EndsWith(".log"))
            {
                System.IO.File.Delete(_filePath);
                Refresh();
            }
        }

        private void DataEntry()
        {
            int x = 0;
            StreamReader file = null;

            try
            {
                var logEntry = (LogEntry)dataEntrys.SelectedValue;
                dataException.Visibility = Visibility.Visible;

                _exception.Clear();
                _exceptionString.Clear();

                file = new StreamReader(_filePath);
                string line;

                while ((line = file.ReadLine()) != null)
                {
                    if (x > logEntry.Line)
                    {
                        if (!string.IsNullOrEmpty(line) && line[0] == '@')
                            break;
                        _exceptionString.Append(line);
                        _exceptionString.Append(Environment.NewLine);
                        _exception.Add(new LogEntry() { Line = x, Message = line });
                    }
                    x++;
                }
            }
            catch
            {
                _exception.Add(new LogEntry() { Line = x, Message = "Log file failed at line " + x });
            }
            finally
            {
                if (file != null) file.Close();
            }
        }

        private void FilterApply()
        {
            _viewSource.View.Refresh();
        }

        private void IdErase()
        {
            var check = _idFilters.Where(f => !f.IsChecked).Count() > 0;
            foreach (var i in _idFilters)
                i.IsChecked = check;
            _viewSource.View.Refresh();
        }

        private void ConfigErase()
        {
            var check = _configFilters.Where(f => !f.IsChecked).Count() > 0;
            foreach (var i in _configFilters)
                i.IsChecked = check;
            _viewSource.View.Refresh();
        }

        private void LevelErase()
        {
            var check = _levelFilters.Where(f => !f.IsChecked).Count() > 0;
            foreach (var i in _levelFilters)
                i.IsChecked = check;
            _viewSource.View.Refresh();
        }

        private void ThreadErase()
        {
            var check = _threadFilters.Where(f => !f.IsChecked).Count() > 0;
            foreach (var i in _threadFilters)
                i.IsChecked = check;
            _viewSource.View.Refresh();
        }

        private void ErrorErase()
        {
            var check = _errorFilters.Where(f => !f.IsChecked).Count() > 0;
            foreach (var i in _errorFilters)
                i.IsChecked = check;
            _viewSource.View.Refresh();
        }

        private void DateFilter()
        {
            if (dateTimePicker.SelectedDate > DateTime.Now.Date)
            {
                dateTimePicker.SelectedDate = DateTime.Now.Date;
                return;
            }

            Refresh();
        }

        private void Filter(object s, FilterEventArgs a)
        {
            var logEntry = (LogEntry)a.Item;

            imageLevel.Source = _levelFilters.Where(f => !f.IsChecked).Count() > 0 ? _filterFilled : _filterEmpty;
            imageError.Source = _errorFilters.Where(f => !f.IsChecked).Count() > 0 ? _filterFilled : _filterEmpty;
            imageThread.Source = _threadFilters.Where(f => !f.IsChecked).Count() > 0 ? _filterFilled : _filterEmpty;

            int count = _levelFilters.Where(f => f.IsChecked).Count(f => f.Item == logEntry.Level);
            count += _errorFilters.Where(f => f.IsChecked).Count(f => f.Item == logEntry.Message);
            count += _threadFilters.Where(f => f.IsChecked).Count(f => f.Item == logEntry.Thread);

            if (count < 3 || logEntry.DateTime.Date != dateTimePicker.SelectedDate)
            {
                a.Accepted = false;
                return;
            }

            a.Accepted = true;
        }

        private void File()
        {
            OpenFileDialog d = new OpenFileDialog() { FileName = "Document", DefaultExt = ".log", Filter = "log documents (.log)|*.log" };
            Nullable<bool> result = d.ShowDialog();

            if (result == true)
                CmdRouter(Cmds.FileLoad.ToString());
        }

        //cmds
        private enum Cmds { Refresh, Clear, Copy, FileLoad, DataEntry, LevelFilter, LevelErase, ThreadFilter, ThreadErase, IdFilter, IdErase, ConfigFilter, ConfigErase, ErrorFilter, ErrorErase, DateTime, DateTimeErase, DateTimeBack, DateTimeForward, Filters, FileErase }

        //events 
        private void ViewSource_Filter(object s, FilterEventArgs a) { Filter(s, a); }
        private void File_OnClick(object s, RoutedEventArgs a) { File(); }
        private void DateTime_Filter(object s, SelectionChangedEventArgs a)
        {
            if (currentDateTime != dateTimePicker.SelectedDate)
                CmdRouter(Cmds.DateTime.ToString());
            currentDateTime = dateTimePicker.SelectedDate;
        }
        private void DataEntry_DoubleClick(object s, MouseButtonEventArgs a) { CmdRouter(Cmds.DataEntry.ToString()); }
    }

    public class LogEntrys : ObservableCollection<LogEntry>{  public int LastLine = -1;}

    public class LogEntry : INotifyPropertyChanged
    {
        public int Line { get; set; }
        public DateTime DateTime { get; set; }
        public string Level { get; set; }
        public int Thread { get; set; }
        public string Id { get; set; }
        public string Config { get; set; }
        public string Message { get; set; }
        public string Method { get; set; }
        public string Type { get; set; }

        //private
        public event PropertyChangedEventHandler PropertyChanged;

        //public
        public LogEntry()
        {

        }

        public LogEntry(int x, string line)
        {
            Line = x;
            try
            {
                var l = line.Split('@');
                DateTime = System.DateTime.Parse(l[1].Split(',')[0]);
                Level = l[2];
                Thread = int.Parse(l[3]);
                Id = l[4];
                Config = l[5];
                Message = l[6];
                Method = l.Length > 7 ? l[7] :"NA";
                Type = l.Length >8 ? l[8] : "NA";
            }
            catch
            {
                Message = "Log file problem cant parse line " + Line;
            }
        }

        //event
        private void NotifyPropertyChanged(string propName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }

    public class CheckedListItem<T> : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private bool isChecked;
        private T item;

        public CheckedListItem()
        {
        
        }

        public CheckedListItem(T item, bool isChecked = false)
        {
            this.item = item;
            this.isChecked = isChecked;
        }

        public T Item
        {
            get
            {
                return item;
            }
            set
            {
                item = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("Item"));
            }
        }

        public bool IsChecked
        {
            get
            {
                return isChecked;
            }
            set
            {
                isChecked = value;
                if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
            }
        }
    }
}
